﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Net;
using System.Net.WebSockets;
using System.Reflection;
using Tnelab.TneCall;
using Tnelab.Common;

namespace Tnelab.WebApp
{
    internal class WebAppHttpServer:LWSTneCallServer
    {
        object _elegateImplementationLock=new object();
        protected ContextSerializer Serializer { get; private set; }
        protected Dictionary<string, object> ServiceContainer { get; } = new Dictionary<string, object>();
        public WebAppHttpServer(string ip, int port) : base(ip, port) { 
            this.Serializer = new ContextSerializer();
            this.Serializer.SetDelegateImplementation(this.DelegateImplementation);
        }
        protected object? DelegateImplementation(ContextSerializerDelegateInvokeInfo invokeInfo)
        {
            lock (_elegateImplementationLock)
            {
                var pathBytes = Encoding.UTF8.GetBytes(invokeInfo.FunctionPath);
                using var outStream = new MemoryStream();
                using var outBw = new BinaryWriter(outStream);
                invokeInfo.ClearProxy.ClearAction = () => {
                    using var outStream = new MemoryStream();
                    using var outBw = new BinaryWriter(outStream);
                    this.Serializer.ToBytes("clear", outBw);
                    this.Serializer.ToBytes(invokeInfo.FunctionPath, outBw);
                    this.CallClient(outStream.ToArray());
                };
                this.Serializer.ToBytes(invokeInfo.FunctionPath, outBw);
                foreach (var arg in invokeInfo.Args)
                {
                    this.Serializer.ToBytes(arg, outBw);
                }
                using var inStream = new MemoryStream(this.CallClient(outStream.ToArray()));
                inStream.Position = 0;
                using var datarBr = new BinaryReader(inStream);
                var rbool = (bool)Serializer.FromBytes(typeof(bool), datarBr);
                if (!rbool)
                {
                    var errorMessage = this.Serializer.FromBytes(typeof(string), datarBr) as string;
                    throw new Exception(errorMessage);
                }
                object? result = null;
                if (invokeInfo.ReturnType != typeof(void))
                    result = this.Serializer.FromBytes(invokeInfo.ReturnType, datarBr);
                return result;
            }
        }
        protected override Stream? OnHttpRequest(HttpListenerRequest request)
        {
            if(request == null||request.Url==null)
                return null;
            if (request.Url.AbsolutePath == "/GetService")
            {
                return GetServiceScriptStream(request);
            }
            else
            {
                return GetManifestResource(request);
            }
        }
        private string GetCodeTypeName(Type type)
        {
            var name = type.FullName;
            if (type.IsGenericType)
            {
                name = name.Split('`')[0];
                name = $"{name}<{String.Join(',', type.GenericTypeArguments.Select(it => GetCodeTypeName(it)))}>";
            }
            return name;
        }
        string GetJsTypeNameForArray(Type type)
        {
            var jsType = "any";
            var tName = GetCodeTypeName(type);
            switch (tName)
            {
                case "System.Int16[]":
                    jsType = "Int16Array";
                    break;
                case "System.UInt16[]":
                    jsType = "Uint16Array";
                    break;
                case "System.Int32[]":
                    jsType = "Int32Array";
                    break;
                case "System.UInt32[]":
                    jsType = "Uint32Array";
                    break;
                case "System.Int64[]":
                    jsType = "BigInt64Array";
                    break;
                case "System.UInt64[]":
                    jsType = "BigUint64Array";
                    break;
                case "System.Double[]":
                    jsType = "Float64Array";
                    break;
                case "System.Decimal[]":
                    jsType = "Float64Array";
                    break;
                case "System.Single[]":
                    jsType = "Float32Array";
                    break;
                case "System.Byte[]":
                    jsType = "Uint8Array";
                    break;
                case "System.SByte[]":
                    jsType = "Int8Array";
                    break;
            }
            return jsType;
        }
        string GetJsTypeNameByTypeName(Type type)
        {
            var tName = GetCodeTypeName(type);
            string jsType = "any";
            switch (tName)
            {
                case "System.Object":
                    jsType = "Object";
                    break;
                case "System.Char":
                case "System.String":
                    jsType = "string";
                    break;
                case "System.Int16":
                case "System.UInt16":
                case "System.Int32":
                case "System.UInt32":
                case "System.Int64":
                case "System.UInt64":
                case "System.Double":
                case "System.Decimal":
                case "System.Single":
                case "System.Byte":
                case "System.SByte":
                    jsType = "number";
                    break;
                case "System.Boolean":
                    jsType = "boolean";
                    break;
                case "System.Void":
                    jsType = "void";
                    break;
            }
            if (jsType == "any")
            {
                if (tName.StartsWith("System.Action"))
                {
                    if (!tName.EndsWith(">"))
                    {
                        jsType = "()=>void";
                    }
                    else
                    {
                        var subTypeList = type.GetGenericArguments();
                        var gvd = new string[subTypeList.Length];
                        for (var i = 0; i < subTypeList.Length; i++)
                        {
                            gvd[i] = $"arg{i}:{GetJsTypeNameByTypeName(subTypeList[i])}";
                        }
                        jsType = $"({String.Join(",", gvd)})=>void";
                    }
                }
                else if (tName.StartsWith("System.Func<"))
                {
                    var subTypeList = type.GetGenericArguments();
                    var paramTypes = new string[subTypeList.Length - 1];
                    var returnType = subTypeList[subTypeList.Length - 1];
                    for (var i = 0; i < paramTypes.Length; i++)
                    {
                        paramTypes[i] = $"arg{i}:{GetJsTypeNameByTypeName(subTypeList[i])}";
                    }
                    jsType = $"({String.Join(",", paramTypes)})=>{GetJsTypeNameByTypeName(returnType)}";
                }
                else if (tName.StartsWith("System.Collections.Generic.IList<") || tName.StartsWith("System.Collections.Generic.List<"))
                {
                    var subType = type.GetGenericArguments()[0];
                    jsType = $"Array<{GetJsTypeNameByTypeName(subType)}>";
                }
                else if (type.IsArray)
                {
                    if (type.GetElementType().GetCustomAttribute(typeof(JsImportAbleAttribute)) != null)
                    {
                        jsType = type.Name;
                    }
                    else
                    {
                        jsType = GetJsTypeNameForArray(type);
                    }
                }
                else
                {
                    if (type.GetCustomAttribute(typeof(JsImportAbleAttribute)) != null)
                    {
                        jsType = type.Name;
                    }
                    else
                    {
                        jsType = tName;
                    }
                }
            }
            return jsType;
        }
        string GetServiceScript(Type serviceType)
        {
            var methods = serviceType.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
            var props = serviceType.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
            var strBuilder = new StringBuilder();
            var splitFlag = "";

            if (serviceType.Namespace != null)
            {
                var namespaceArr = serviceType.Namespace.Split('.');
                for (var i = 0; i < namespaceArr.Length; i++)
                {
                    strBuilder.AppendLine($"{splitFlag}var {namespaceArr[i]};");
                    strBuilder.AppendLine($"{splitFlag}(function ({namespaceArr[i]}) {{");
                    splitFlag += "  ";
                }
            }
            strBuilder.Append(splitFlag).AppendLine($"class {serviceType.Name} {{");
            splitFlag += "  ";
            //生成属性
            foreach (var propInfo in props)
            {
                strBuilder.Append(splitFlag).AppendLine($"set {propInfo.Name}(value) {{this.{propInfo.Name}_=value; }}");
                strBuilder.Append(splitFlag).AppendLine($"get {propInfo.Name}() {{ return this.{propInfo.Name}_; }}");
            }
            if (props.Length != 0)
            {
                //生成属性反射代码
                //返回属性列表
                strBuilder.Append(splitFlag).AppendLine("GetPropInfos(){");
                splitFlag += "  ";
                strBuilder.Append(splitFlag).AppendLine("let propInfos = [];");
                foreach (var propInfo in props)
                {
                    strBuilder.Append(splitFlag).AppendLine("propInfos.push({TypeName:\"" + GetCodeTypeName(propInfo.PropertyType) + "\",JsTypeName:\"" + GetJsTypeNameByTypeName(propInfo.PropertyType) + "\",Name:\"" + propInfo.Name + "\"});");
                }
                strBuilder.Append(splitFlag).AppendLine("return propInfos;");
                splitFlag = splitFlag.Remove(0, 2);
                strBuilder.Append(splitFlag).AppendLine("}");
                //获取属性值
                strBuilder.Append(splitFlag).AppendLine("GetValueByPropName(name){");
                splitFlag += "  ";
                strBuilder.Append(splitFlag).AppendLine("switch (name) {");
                splitFlag += "  ";
                foreach (var propInfo in props)
                {
                    strBuilder.Append(splitFlag).AppendLine($"case \"{propInfo.Name}\":");
                    splitFlag += "  ";
                    strBuilder.Append(splitFlag).AppendLine($"return this.{propInfo.Name}_;");
                    splitFlag = splitFlag.Remove(0, 2);
                }
                strBuilder.Append(splitFlag).AppendLine("default:");
                splitFlag += "  ";
                strBuilder.Append(splitFlag).AppendLine("throw \"对象不存在\"+name+\"属性\";");
                splitFlag = splitFlag.Remove(0, 2);
                splitFlag = splitFlag.Remove(0, 2);
                strBuilder.Append(splitFlag).AppendLine("}");
                splitFlag = splitFlag.Remove(0, 2);
                strBuilder.Append(splitFlag).AppendLine("}");
                //设置属性值
                strBuilder.Append(splitFlag).AppendLine("SetValueByPropName(name,val){");
                strBuilder.Append(splitFlag).AppendLine("\t\t\tswitch (name) {");
                splitFlag += "  ";
                foreach (var propInfo in props)
                {
                    strBuilder.Append(splitFlag).AppendLine($"case \"{propInfo.Name}\":");
                    splitFlag += "  ";
                    strBuilder.Append(splitFlag).AppendLine($"this.{propInfo.Name}_=val;");
                    strBuilder.Append(splitFlag).AppendLine($"return;");
                    splitFlag = splitFlag.Remove(0, 2);
                }
                strBuilder.Append(splitFlag).AppendLine("default:");
                splitFlag += "  ";
                strBuilder.Append(splitFlag).AppendLine("throw \"对象不存在\"+name+\"属性\";");
                splitFlag = splitFlag.Remove(0, 2);
                strBuilder.Append(splitFlag).AppendLine("}");
                splitFlag = splitFlag.Remove(0, 2);
                strBuilder.Append(splitFlag).AppendLine("}");
            }
            //生成方法
            foreach (var method in methods)
            {
                if (method.IsSpecialName)
                    continue;
                strBuilder.Append(splitFlag).Append($"async {method.Name}(");
                var parameters = method.GetParameters();
                var args = parameters.Select(it => it.Name);
                strBuilder.Append(String.Join(',', args)).AppendLine("){");
                splitFlag += "  ";
                strBuilder.Append(splitFlag).AppendLine("let argsInfos_ = new Array();");
                strBuilder.Append(splitFlag).AppendLine("let actionOrFuncArgInfosList_ = new Array();");
                strBuilder.Append(splitFlag).AppendLine($"argsInfos_.push({{ CSTypeName: \"{GetCodeTypeName(method.ReturnType)}\", JSTypeName: \"{GetJsTypeNameByTypeName(method.ReturnType)}\", Value: undefined }});");
                foreach (var parameter in parameters)
                {
                    strBuilder.Append(splitFlag).AppendLine($"argsInfos_.push({{ CSTypeName: \"{GetCodeTypeName(parameter.ParameterType)}\", JSTypeName: \"{GetJsTypeNameByTypeName(parameter.ParameterType)}\", Value: {parameter.Name} }});");
                    if (parameter.ParameterType.FullName.StartsWith("System.Func`") || parameter.ParameterType.FullName.StartsWith("System.Action"))
                    {
                        var isFunc = parameter.ParameterType.FullName.StartsWith("System.Func~") ? true : false;
                        //输出代码
                        strBuilder.Append(splitFlag).AppendLine("actionOrFuncArgInfosList_.push(new Array());");
                        var subTypeList = parameter.ParameterType.GetGenericArguments();
                        for (var i = 0; i < subTypeList.Length; i++)
                        {
                            strBuilder.Append(splitFlag).AppendLine($"actionOrFuncArgInfosList_[actionOrFuncArgInfosList_.length-1].push({{CSTypeName:\"{GetCodeTypeName(subTypeList[i])}\",JSTypeName:\"{GetJsTypeNameByTypeName(subTypeList[i])}\"}});");
                        }
                    }
                }
                strBuilder.Append(splitFlag).AppendLine($"return await Tnelab.InvokeNative(\"{serviceType.FullName}\", \"{method.Name}\", argsInfos_, actionOrFuncArgInfosList_);");
                splitFlag = splitFlag.Remove(0, 2);
                strBuilder.Append(splitFlag).AppendLine("}");
            }
            splitFlag = splitFlag.Remove(0, 2);
            strBuilder.Append(splitFlag).AppendLine("}");
            if (serviceType.Namespace != null)
            {
                strBuilder.AppendLine($"{splitFlag}{serviceType.Namespace}.{serviceType.Name}={serviceType.Name};");
                var namespaceArr = serviceType.Namespace.Split('.');
                for (var i = namespaceArr.Length - 1; i >= 0; i--)
                {
                    var b = "";
                    for (var j = 0; j < i; j++)
                    {
                        b += namespaceArr[j];
                    }
                    if (b.Length > 0)
                    {
                        b += ".";
                    }
                    var es = $"= {b}{namespaceArr[i]}";
                    if (i == 0)
                        es = "";
                    splitFlag = splitFlag.Remove(0, 2);
                    strBuilder.AppendLine($"{splitFlag}}})({namespaceArr[i]} {es} || ({b}{namespaceArr[i]} = {{}}));");
                    strBuilder.AppendLine($"{splitFlag}{serviceType.Name}={splitFlag}{serviceType.Namespace}.{serviceType.Name};");
                }
            }
            var output = strBuilder.ToString();
            return output;
        }
        Stream GetServiceScriptStream(HttpListenerRequest request)
        {
            var temp = request.Url.Query.Split('=')[1];
            var temps = temp.Split(',');
            var assemblyName = temps[0];
            var typeName = temps[1];
            if (typeName.IndexOf('.') == -1)
            {
                typeName = $"{assemblyName}.{typeName}";
            }
            var assembly=Assembly.Load(assemblyName);
            var serviceType=assembly.GetType(typeName);
            if (serviceType == null)
                throw new Exception($"找不到类型:{typeName}");
            if (!ServiceContainer.ContainsKey(typeName))
            {
                var obj= Activator.CreateInstance(serviceType);
                if(obj==null)
                    throw new Exception($"无法创建对象:{serviceType.FullName}");
                ServiceContainer.Add(typeName, obj);
            }
            var script=GetServiceScript(serviceType);
            var buffer=Encoding.UTF8.GetBytes(script);
            return new MemoryStream(buffer);
        }
        Stream? GetManifestResource(HttpListenerRequest request)
        {
            var path = request.Url.AbsolutePath.Remove(0, 1);
            if (path == "favicon.ico")
            {
                return null;
            }
            return WebApplication.GetManifestResource(path);
        }
        private object GetService(string typeName)
        {
            return this.ServiceContainer[typeName];
        }
        protected override Stream OnCallServer(byte[] datas)
        {
            object result = null;
            using var inStream = new MemoryStream(datas);
            using var inBr = new BinaryReader(inStream);
            var id = (int)this.Serializer.FromBytes(typeof(int), inBr);
            try
            {
                var path = (string)this.Serializer.FromBytes(typeof(string), inBr);

                var ldi = path.LastIndexOf(".");
                var typeName = path.Substring(0, ldi);
                var methodName = path.Substring(ldi + 1, path.Length - ldi - 1);
                var service = GetService(typeName);
                var methodInfo = service.GetType().GetMethod(methodName);

                var argInfos = methodInfo.GetParameters();
                var args = new object[argInfos.Length];
                for (var i = 0; i < args.Length; i++)
                {
                    args[i] = this.Serializer.FromBytes(argInfos[i].ParameterType, inBr);
                }
                result = methodInfo.Invoke(service, args);

                var outStream = new MemoryStream();
                var outBw = new BinaryWriter(outStream);
                this.Serializer.ToBytes(id, outBw);
                this.Serializer.ToBytes(true, outBw);
                if (methodInfo.ReturnType != typeof(void))
                    this.Serializer.ToBytes(result, outBw);
                return outStream;
            }
            catch (Exception ex)
            {
                var outStream = new MemoryStream();
                var outBw = new BinaryWriter(outStream);
                this.Serializer.ToBytes(id, outBw);
                this.Serializer.ToBytes(false, outBw);
                this.Serializer.ToBytes(ex.ToString(), outBw);
                return outStream;
            }
        }
    }
}
